*! version 1.0.5  26nov2018
program define spmat, rclass

	version 11.1
	
	gettoken sub 0 : 0 , parse(" ,")
	
 	local len = length(`"`sub'"')
        if `len'==0 {
               di as err "No subcommand specified"
               exit 198
        }
        
	// some spmat subcommands create tempnames used by Mata
	// make sure to drop the tempnames after a subcommand is terminated
	
	if `"`sub'"'==substr("idistance",1,max(5,`len')) {
		SPMAT_idistance `0'
	}
	else if `"`sub'"'==substr("contiguity",1,max(3,`len')) {
		cap noi SPMAT_contiguity `0'
		SPMAT_drop_tempnames `matanames'
	}
	else if `"`sub'"'==substr("use",1,max(3,`len')) {
		SPMAT_read `0'
	}
	else if `"`sub'"'==substr("import",1,max(2,`len')) {
                SPMAT_import `0'
        }
	else if `"`sub'"'==substr("export",1,max(2,`len')) {
                SPMAT_export `0'
        }
	else if `"`sub'"'==substr("save",1,max(2,`len')) {
                SPMAT_save `0'
        }
	else if `"`sub'"'=="drop" {
                SPMAT_drop `0'
        }
	else if `"`sub'"'==substr("graph",1,max(2,`len')) {
                SPMAT_graph `0'
        }
	else if `"`sub'"'==substr("putmatrix",1,max(3,`len')) {
		syntax [ namelist(max=3)] [, * ]
		
		local w : word count `namelist'
		if `w'==0 {
			di "{err}name of spmat object required"
			exit 498
		}
		local objname : word 1 of `namelist'
		if `w'>2 local old _old
		cap noi SPMAT_putmatrix`old' `0'
		local rc = _rc
		if `rc' {
                	capture SPMAT_drop_tempnames `matanames'
			capture mata: SPMAT_fcheck(`objname')
			if _rc capture mata mata drop `objname'
		}
		exit `rc'
        }
	else if `"`sub'"'==substr("getmatrix",1,max(3,`len')) {
		syntax namelist(min=1 max=3) [, * ]
		local w : word count `namelist'
		if `w'>2 local old _old
                SPMAT_getmatrix`old' `0'
        }
        else if `"`sub'"'==substr("permute",1,max(3,`len')) {
                SPMAT_permute `0'
        }
        else if `"`sub'"'==substr("dta",1,max(3,`len')) {
                cap noi SPMAT_dta `0'
                SPMAT_drop_tempnames `matanames'
        }
	else if `"`sub'"'==substr("summarize",1,max(2,`len')) {
		SPMAT_summarize `0'
		return add
	}
        else if `"`sub'"'==substr("note",1,max(4,`len')) {
                cap noi SPMAT_note `0'
                SPMAT_drop_tempnames `matanames'
        }
        else if `"`sub'"'==substr("tobanded",1,max(3,`len')) {
                SPMAT_tobanded `0'
        }
        else if `"`sub'"'==substr("copy",1,max(4,`len')) {
                SPMAT_copy `0'
        }
        else if `"`sub'"'==substr("lag",1,max(3,`len')) {
                SPMAT_lag `0'
        }
        else if `"`sub'"'==substr("eigenvalues",1,max(3,`len')) {
                SPMAT_eigenvalues `0'
        }
        else if `"`sub'"'==substr("idmatch",1,max(2,`len')) {
                SPMAT_idmatch `0'	// not documented
        }
 	else {
                di as error `"`sub' unknown subcommand"'
                exit 198
        }
        
end

program define SPMAT_drop_tempnames
	
	syntax [anything]
	
	local rc = c(rc)
	
        foreach m of local 0 {
		capture mata: mata drop `m'
	}
	if `rc' exit `rc'
	
end

program define SPMAT_contiguity, sortpreserve

	syntax name [if] [in] using/, 		///
		id(varname numeric) 		///
		[				///
		rook				///
		NORMalize(string)		///
		TOLerance(real .0000001) 	///
		BANDed				///
		replace				///
		saving(string asis)	 	///
		noMATrix			///
		]
	
	if "`matrix'"=="nomatrix" & `"`saving'"'=="" {
		di "{err}saving() required with option nomatrix"
		exit 498
	}
	
	SPMAT_normparse,  `normalize'
	local normalize `r(normalize)'
	
	marksample touse
	
	qui preserve
	
	tempvar index index2 gr N
	tempname map
	tempfile mapping gr1 gr2 n2 n3 jb
	
	c_local matanames `map'
	
	qui keep if `touse'
	
	qui count
	local size = `r(N)'
	
	// ++++++++++++ mapping between `id' and 1...n
	keep `id'
	qui gen double `index' = _n
	qui save `mapping'
	mata : `map'=st_data(.,"`id' `index'")
	
	sort `id'
	capture by `id': assert _N==1
	if _rc {
		di as err "`id' values must be unique"
		exit 498
	}
		
	if ("`normalize'"=="spectral" & "`banded'"=="banded") {
		di "{err}spectral normalization not allowed with " 	///
			"option banded"
		exit 498
	}
	
	local tolerance = `tolerance'
	
	if "`rook'"=="" {		// ++++++++++++++++++ vertex contiguity
		
		capture rename `id' _ID
		qui merge 1:n _ID using `"`using'"'
		qui count if _merge == 1
		if r(N) > 0 {
			di as err "Some observations in master dataset "    ///
				"are not in coordinates dataset"
			exit 498
		}
		qui keep if _merge==3
		keep `index' _X _Y
		order `index' _X _Y
		
		qui drop if _X==.

		qui replace _X = round(_X,`tolerance')
		qui replace _Y = round(_Y,`tolerance')
		
		sort _X _Y
		qui by _X _Y: gen double `gr'=_n==1
		qui replace `gr'=sum(`gr')
		
		drop _X _Y
		
		sort `gr' 		// there will still be multiple 
					// obs per `gr' `index'
					// but mata will process them faster 

		qui by `gr': gen `N'=_N
		qui drop if `N'==1
		
		qui count
		if `r(N)'==0 {
			di
			di "{res}No neighbors found"
			di
			exit 0
		}
		
		qui replace `N' = `N'==2		
	}
	else {				// ++++++++++++++++++++ edge contiguity
				
		tempvar orderu _X2 _Y2 slope intercept vline v edge
		tempvar minX maxX minY maxY
		
		// +++++++++++ shp2dta does not create an order var, create one
		qui use _ID _X _Y using `"`using'"', clear
		capture rename _ID `id'
		qui gen double `orderu' = _n
		sort `id'
		qui merge n:1 `id' using `mapping'
		qui count if _merge == 2
		if r(N) > 0 {
			di as err "Some observations in master dataset "    ///
				"are not in coordinates dataset"
			exit 498
		}
		qui keep if _merge==3
		drop _merge
		qui assert `index'!=.
		keep `index' `orderu' _X _Y
		sort `index' `orderu'
		drop `orderu'
		
		qui gen double `_X2' = _X[_n-1]
		qui gen double `_Y2' = _Y[_n-1]
		qui drop if _X==. | `_X2'==.
		
		qui gen double `slope' = (_Y-`_Y2') / (_X-`_X2')
		qui gen double `intercept' = _Y-`slope'*_X
		qui gen double `vline' = _X if `slope'==.
		
		qui replace `slope' = round(`slope',`tolerance')
		qui replace `intercept' = round(`intercept',`tolerance')
		qui replace `vline' = round(`vline',`tolerance')
		
		sort `intercept' `slope' `vline'
		qui by `intercept' `slope' `vline': gen double `gr' = _n==1
		qui replace `gr'=sum(`gr')
		
		drop `slope' `intercept'
		qui gen byte `v' = `vline'==.
		drop `vline'
	
		sort `gr'
		qui by `gr': gen `N'=_N
		qui drop if `N'==1
		
		qui count
		if `r(N)'==0 {
			di
			di "{res}No neighbors found"
			di
			exit 0
		}
		
		qui keep `index' _X _Y `gr' `_X2' `_Y2' `v' `N'
		
		qui gen double `minX' = min(_X,`_X2')
		qui gen double `maxX' = max(_X,`_X2')
		qui gen double `minY' = min(_Y,`_Y2')
		qui gen double `maxY' = max(_Y,`_Y2')
		
		drop _X `_X2' _Y `_Y2'
		sort `gr'
		
		qui replace `N' = `N'==2
		
		keep `index' `gr' `N' `v' `minX' `maxX' `minY' `maxY'
		
		order `index' `gr' `N' `v' `minX' `maxX' `minY' `maxY'
	}
	
	if "`replace'"=="replace" capture spmat drop `namelist'
	
	if `"`saving'"'!="" {
		gettoken file replace : saving, parse(" ,")
		if "`replace'"!="" {
			local replace = trim(subinstr("`replace'",",","",.))
		}
	}
	
	mata : SPMAT_contiguity("`namelist'",`map',"`normalize'",  ///
		`size',"`rook'","`matrix'","`banded'","`file'","`replace'")
	
	mata : mata drop `map'
	
	restore
	
end

program define SPMAT_idistance, sortpreserve
	
	syntax anything [if] [in] , 				///
		id(varname numeric) 				///
		[						///
		DFunction(string)				///
		NORMalize(string)				///
		BTRuncate(numlist >0 min=2 max=2)		///
		DTRuncate(numlist >=0 integer min=1 max=2)	///
		VTRuncate(string)				///
		BANDed						///
		replace						///
		knn(string)					/// undocumented
		]
	
	marksample touse, novarlist
	
	gettoken objname coordinates : anything
	
	if "`coordinates'"=="" {
		di "{err}at least one coordinate variable required"
		exit 498
	}
	capture confirm numeric variable `coordinates'
	if _rc {
		di "{err}coordinate variables must be numeric"
		exit 498
	}
	foreach c of local coordinates {
		capture assert !missing(`c') if `touse'
		if _rc {
		       di "{err}coordinate variable {bf:`c'} has missing values"
			exit 198
		}
	}
	
	tempvar order
	qui gen double `order' = _n
	sort `coordinates'
	capture by `coordinates': assert _N==1
	if _rc {
		di "{err}Two or more observations have the same coordinates"
		exit 498
	}
	sort `order'
	
	local ncoor : word count `coordinates'
	
	// parse knn option
	
	if "`knn'"!="" {
		gettoken knn rest : knn, parse(",")
		local rest : subinstr local rest "," " "
		local 0 ", knn(`knn') `rest'"
		syntax , knn(numlist max=1 >0) [ TIES(string) ]
		if "`ties'"!="" {
			if "`ties'"!="keep" & "`ties'"!="drop" {
				di "{err}invalid {cmd:ties()} option"
				exit 498
			}
		}
	}
	
	local btr = ("`btruncate'"!="")
	local dtr = ("`dtruncate'"!="")
	local vtr = ("`vtruncate'"!="")
	local ktr = ("`knn'"!="")
	local trs = `btr'+`dtr'+`vtr'
	if `trs'>1 {
		di "{err}only one of btruncate(), dtruncate(), or " _c
			di "vtruncate() is allowed"
		exit 498
	}
	
	SPMAT_normparse,  `normalize'
	local normalize `r(normalize)'
	
	if ("`banded'"=="banded" & `trs'==0 & `ktr'==0) {
		di "{err}one of truncate() required"
		exit 498
	}
	
	if ("`banded'"=="banded" & "`normalize'"=="spectral") {
		di "{err}spectral normalization not allowed with " 	///
			"option {cmd:banded}"
		exit 498
	}
	
	// +++++++++++++++++++++++++++++++++++++++++++ process dfunction option
	
	// for either haversine the number of coordinates must =2
	// miles can be specified only with haversine
	
	local 0 "_`dfunction'"
	capture syntax [ namelist(name=dfunction) ] [, MIles ]
	if _rc {
		di "{err}option {cmd:dfunction()} incorrectly specified"
		exit 498
	}
	local dfunction : subinstr local dfunction "_" "" 
	if "`dfunction'"=="" local dfunction "euclidean"
	
	local 0 ",`dfunction'"
	
	local 0 ",`dfunction'"
	syntax [, EUClidean DHAVersine RHAVersine * ]  // Minkowski passed in *
	
	local minkowski `options'
	
	if "`minkowski'" != "" {
		capture confirm integer number `minkowski'
		if _rc {
			di "{err}option {cmd:dfunction()} incorrectly specified"
			exit 498
		}
		else {
			if `options'<1 {
				di "{err}Minkowski distance must be > 0"
				exit 498
			}
			local power `minkowski'
		}
	}
	
	if "`power'"=="" local power 2
	
	opts_exclusive "`euclidean' `dhaversine' `rhaversine' `minkowski'"
	
	if ("`dhaversine'`rhaversine'"!="" & `ncoor'!=2) {
		di "{err}{cmd:`dhaversine'`rhaversine'} requires "  ///
					"two coordinate variables"
		exit 498
	}
	
	if ("`dhaversine'`rhaversine'"=="" & "`miles'"!="") {
		di "{err}{cmd:miles} not allowed with option "	///
					"{cmd:`euclidean'`minkowski'}"
		exit 498
	}
	
	if "`miles'"=="" {
		local dscale = "km"
	}
	else local dscale = "miles"
	
	if "`euclidean'`minkowski'"!="" {
		local type "reals"
	}
	else if "`dhaversine'"!="" {
		local type "degrees"
	}
	else local type
	
	tempvar prob
	
	markout `touse' `coordinates'
	
	preserve
	quietly keep if `touse'
	
	qui count
	local maxn = r(N)-1
	
	if ("`knn'"!="") {
		capture confirm integer number `knn'
		if _rc {
			di "{err}knn() must be an integer"
			exit 498
		}
		
		if (`knn'>`maxn') {
			di "{err}knn() must be <= `maxn'"
			exit 498
		}
	}
	else local knn=0
	
	local trtype = ("`btruncate'"!="")*1 + ("`dtruncate'"!="")*2 + ///
		("`vtruncate'"!="")*3
	
	local num1 = 0
	local num2 = 0
	
	if "`btruncate'"!="" {
		local num1 : word 1 of `btruncate'
		local num2 : word 2 of `btruncate'
		if `num2' <= `num1' {
			di "{err}number of bins must be greater than the " _c
				di "bin you are truncating at"
			exit 498
		}
	}
	
	if "`dtruncate'"!="" {
		local num1 : word 1 of `dtruncate'
		local num2 : word 2 of `dtruncate'
		if "`num2'"=="" local num2 = `num1'
		qui count
		local limit = floor(`r(N)'/4)
		if (`num1' > `limit' | `num2' > `limit') {
			di "{err}dtruncate() arg must be <= `limit'"
			exit 498
		}
	}
	
	if "`vtruncate'"!="" {
		local nval : word count `vtruncate'
		if `nval'>1 {
			di "{err}{cmd:vtruncate()} accepts only one number"
			exit 498
		}
		capture local num1 = `vtruncate'
		if _rc {
			di "{err}{cmd:`vtruncate'} is not a valid number"
			exit 498
		}
		capture confirm number `num1'
		if _rc {
			di "{err}{cmd:`vtruncate'} did not evaluate to a number"
			exit 498
		}
		local num2 = 0
	}
	
	if "`replace'"=="replace" {
		capture spmat drop `objname'
	}
	
	mata : SPMAT_idistance`banded'("`objname'" ,"`id'" , 		///
		"`coordinates'", "`normalize'", `trtype', `num1', 	///
		`num2', "`type'",`power',"`dscale'")
	
	restore
	
end

program define SPMAT_putmatrix, rclass
	
	syntax namelist(max=2) [ ,					///
		IDISTance 						///
		NORMalize(string)					///
		Bands(numlist integer >=0 min=1 max=2) 			///
		id(string)						///
		eig(string)						///
		replace							///
		force							///
		]
	
	local objname : word 1 of `namelist'
	local matname : word 2 of `namelist'
	
	if "`matname'`id'`eig'"=="" {
		di "{err}at least one of matname, id, eig required"
		exit 498
	}
	
	if "`replace'"=="" {
		capture mata : SPMAT_assert_object("`objname'")
		if !_rc {
			di "{err}spmat object {cmd:`objname'} already exists"
			exit 498
		}
		if "`matname'"=="" {
			di "{err}matname required for a new spmat object"
			exit 498
		}
		local replace 0
	}
	else {
		capture mata : SPMAT_assert_object("`objname'")
		if _rc local replace 0
		else local replace 1
	}
	
	if "`matname'"=="" {
		if "`idistance'"!="" {
			di "{err}idistance can only be specified with matname"
			exit 498
		}
		if "`normalize'"!="" {
			di "{err}normalize can only be specified with matname"
			exit 498
		}
		if "`bands'"!="" {
			di "{err}bands can only be specified with matname"
			exit 498
		}
	}
	else {
		capture mata mata describe `matname'
		if _rc {
			di "{err}matrix `matname' not found"
			exit 498
		}
	}
	
	if "`id'" != "" {
		
		tempname vecname
		c_local matanames `vecname'
		
		local w : word count `id'
		if `w'>1 {
			di "{err}only one argument allowed in id()"
			exit 498
		}
		
		capture confirm variable `id'
		local sid = _rc
		capture mata mata describe `id'
		local mid = _rc
		if (!`sid' & !`mid') {
			di "{err}ambiguous id({cmd:`id'}): {cmd:`id'} "	///
				"exists in both Stata and Mata"
			exit 498
		}
		
		if !`sid' {
			capture confirm numeric variable `id'
			if _rc {
				di "{err}`id' must be a numeric variable"
				exit 498
			}
			capture assert `id' != .
			if _rc {
				di "{err}`id' contains missing values"
				exit 498
			}		
			mata :`vecname'=st_data(.,"`id'")
		}
		else {
			capture mata: `vecname' = `id'
			if _rc {
				di "{err}`id' not found"
				exit 498
			}
		}
	}
	
	if "`matname'"== "" {
		local matname .
		local hasm 0
	}
	else local hasm 1
	
	if "`id'"== "" {
		local hasid 0
		local vecname .
	}
	else local hasid 1
	
	if "`eig'"== "" {
		local hase 0
		local eig .
	}
	else {
		capture mata: SPREG_eigen_check(`eig')
		if _rc {
			di "{err}`eig' must be a numeric rowvector"
			exit 498
		}
		local hase 1
	}
	
	SPMAT_normparse,  `normalize'
	local normalize `r(normalize)'
	
	gettoken lb ub : bands
	if "`lb'"=="" local lb=-1
	if "`ub'"=="" local ub=`lb'
	
	if ("`normalize'"=="spectral" & "`bands'"!="") {
		di "{err}spectral normalization not allowed "		///
			"with banded matrices"
		exit 498
	}
	
	if "`objname'"=="`matname'" {
		di "{err}matname must be different from objname"
		exit 498
	}
	
	cap noi mata : SPMAT_putmatrix("`objname'",`hasm',`hasid',	///
		`hase',"`idistance'","`normalize'",`lb',`ub',`matname',	///
		`vecname',`eig')
	exit _rc
		
end

program define SPMAT_putmatrix_old
	
	syntax namelist [ , 						///
		IDISTance 						///
		NORMalize(string)					///
		Bands(numlist integer >=0 min=1 max=2) 			///
		id(string)						///
		replace							///
		force							///
		]
	
	local words : word count `namelist'
	if (`words'<2 | `words'>3) {
		di "{err}incorrect number of arguments"
		exit 498
	}
	
	local objname : word 1 of `namelist'
	local matname : word 2 of `namelist'
	local vecname : word 3 of `namelist'
	
	if "`vecname'"!="" {
		gettoken bef aft : 0, parse(",")
		local bef : subinstr local bef  "`vecname'" ""
		if ("`aft'"!="") local aft `aft' id(`vecname')
		else local aft `aft' , id(`vecname')
		
		local 0 `bef' `aft'
	}
	
	SPMAT_putmatrix `0'
end

program define SPMAT_getmatrix
	
	syntax namelist(min=1 max=2) [, id(string) eig(string) ]
	
	local objname : word 1 of `namelist'
	local matname : word 2 of `namelist'
	
	capture mata : SPMAT_assert_object("`objname'")
	if _rc SPMAT_error `objname'
	
	if ("`objname'"=="`matname'") {
		di "{err}matname must be different from objname"
		exit 498
	}
	if ("`objname'"=="`id'") {
		di "{err}id must be different from objname"
		exit 498
	}
	if ("`objname'"=="`eig'") {
		di "{err}eig must be different from objname"
		exit 498
	}
	
	if "`matname'"!="" {
		if ("`matname'"=="`id'") {
			di "{err}id must be different from matname"
			exit 498
		}
		if ("`matname'"=="`eig'") {
			di "{err}eig must be different from matname"
			exit 498
		}
	}
	
	if "`id'"!="" {
		if ("`id'"=="`eig'") {
			di "{err}eig must be different from id"
			exit 498
		}
	}
	
	if "`matname'"!="" mata : `matname' = SPMAT_getsel("`objname'",2)
	if "`id'"!="" mata : `id' = SPMAT_getsel("`objname'",1)
	if "`eig'"!="" mata : `eig' = SPMAT_getsel("`objname'",12)
	
end

program define SPMAT_getmatrix_old
	
	syntax namelist [, * ]
	
	local objname : word 1 of `namelist'
	local matname : word 2 of `namelist'
	local vecname : word 3 of `namelist'
	
	local args : word count `namelist'
	
	if `args'>3 {
		di "{err}too many arguments"
		exit 498
	}
	
	if "`vecname'"!="" {
		gettoken bef aft : 0, parse(",")
		local bef : subinstr local bef  "`vecname'" ""
		if ("`aft'"!="") local aft `aft' id(`vecname')
		else local aft `aft' , id(`vecname')
		local 0 `bef' `aft'
	}
	
	SPMAT_getmatrix `0'
end

program define SPMAT_summarize, rclass
	
	syntax name(name=objname)				///
		[ ,						///
		LInks						///
		DETail						///
		DTRuncate(numlist >=0 integer min=1 max=2)	///
		VTRuncate(string)				///
		BTRuncate(numlist >0 min=2 max=2)		///
		BANDed						///
		]
	
	capture mata : SPMAT_assert_object("`objname'")
	if _rc SPMAT_error `objname'
	
	local btr = ("`btruncate'"!="")
	local dtr = ("`dtruncate'"!="")
	local vtr = ("`vtruncate'"!="")
	local con = ("`banded'"!="")
	local trs = `btr'+`dtr'+`vtr'+`con'
	
	if `trs'>1 {
		di "{err}only one of btruncate(), dtruncate(), "	///
				"vtruncate(), or banded is allowed"
		exit 498
	}
	
	if `trs'>0 & "`detail'"!="" {
		if (`btr') local opt "btruncate()"
		if (`dtr') local opt "dtruncate()"
		if (`vtr') local opt "vtruncate()"
		if (`con') local opt "banded"
		di "{err}option detail not allowed with {inp}`opt'"
		exit 498
	}
	
	if "`detail'"!="" & "`links'"=="" {
		di "{err}option links required with detail"
		exit 498
	}
	
	if "`btruncate'"!="" {
		local atbin : word 1 of `btruncate'
		local nbins : word 2 of `btruncate'
		if `nbins' < 2 {
			di "{err}number of bins must be greater than 1"
			exit 498
		}
		if `nbins' <= `atbin' {
			di "{err}number of bins must be greater than the " _c
				di "bin you are truncating at"
			exit 498
		}
		mata : SPMAT_summarize("`objname'",		///
			"`links'","`detail'",1,`atbin',`nbins')
		if _rc SPMAT_error `objname'
	}
	else if "`dtruncate'"!="" {
		local below : word 1 of `dtruncate'
		local above : word 2 of `dtruncate'
		if "`above'"=="" local above = `below'
		mata : SPMAT_summarize("`objname'",		///
			"`links'","`detail'",2,`below',`above')
		if _rc SPMAT_error `objname'
	}
	else if "`vtruncate'"!="" {
		local nval : word count `vtruncate'
		if `nval'>1 {
			di "{err}{cmd:vtruncate()} accepts only one number"
			exit 498
		}
		capture local num1 = `vtruncate'
		if _rc {
			di "{err}{cmd:`vtruncate'} is not a valid number"
			exit 498
		}
		capture confirm number `num1'
		if _rc {
			di "{err}{cmd:`vtruncate'} did not evaluate to a number"
			exit 498
		}
		mata : SPMAT_summarize("`objname'",		///
			"`links'","`detail'",3,`num1')
		if _rc SPMAT_error `objname'
	}
	else if "`banded'"!="" {
		mata : SPMAT_summarize("`objname'",		///
			"`links'","`detail'",4)
		if _rc SPMAT_error `objname'
	}
	else {	// plain summary
		mata : SPMAT_summarize("`objname'",		///
			"`links'","`detail'",0)
		if _rc SPMAT_error `objname'
	}
	
	return scalar eig = r(eigen)
	
	if "`links'"=="links" {
		return scalar ltotal = r(total)
		return scalar lmax = r(max)
		return scalar lmean = r(mean)
		return scalar lmin = r(min)
	}
	else {
		return scalar max = r(max)
		return scalar mean = r(mean)
		return scalar min0 = r(min0)
		return scalar min = r(min)
	}
	
	local n = r(n)
	local b = r(b)
	
	if (`n'!=`b' | `trs'>0) {
		return scalar canband = r(canband)
		return scalar uband = r(uband)
		return scalar lband = r(lband)
	}
	
	return scalar n = r(n)
	return scalar b = r(b)
	
end

program define SPMAT_note
	
	syntax anything [ , replace ]
		
	gettoken obj rest : 0, parse(": ")
	gettoken colon rest : rest, parse(": ")
	
	if "`colon'"=="drop" { // ++++++++++++++++++++++++++++++++++++ drop note
		capture mata : SPMAT_note("`obj'",`"`note'"',"drop")
		if _rc SPMAT_error `obj'
		exit 0
	}
	
	if !("`colon'"=="" | "`colon'"==":") {
		di "{err}invalid syntax"
		exit 498
	}
	
	if "`colon'"=="" { // ++++++++++++++++++++++++++++++++++++ display note
		tempname note
		c_local matanames `note'
		capture mata : `note' = SPMAT_getsel("`obj'",7)
		if _rc SPMAT_error `obj'
		mata : `note'
		exit 0
	}
	else if "`colon'"==":" {
		gettoken note suffix : rest, parse(",")
		if "`suffix'"!= "" {
			gettoken comma suffix : suffix, parse(", ")
			
			local suffix = trim(`"`suffix'"')
			
			if "`suffix'"=="replace" {
				// +++++++++++++++++++++++++++++++ replace note	
				capture mata : SPMAT_note("`obj'",	///
					`"`note'"',"replace")
				if _rc SPMAT_error `obj'
			}
			else { // +++++++++++++++++++++++++++++++++ append note
				capture mata : SPMAT_note("`obj'",`"`note'"',"")
				if _rc SPMAT_error `objname'
			}
		}
		else { // +++++++++++++++++++++++++++++++++++++++++ append note
			capture mata : SPMAT_note("`obj'",`"`note'"',"")
			if _rc SPMAT_error `obj'
		}
	}
	else {
		di "{err}invalid syntax"
		exit 498
	}
	
end

program define SPMAT_tobanded
	
	syntax anything 					///
		[ ,						///
		BTRuncate(numlist >=0 min=2 max=2)		///
		DTRuncate(numlist >=0 integer min=1 max=2)	///
		VTRuncate(string)				///
		replace 					///
		]
	
	local btr = ("`btruncate'"!="")
	local dtr = ("`dtruncate'"!="")
	local vtr = ("`vtruncate'"!="")
	local trs = `btr'+`dtr'+`vtr'
	
	if `trs'>1 {
		di "{err}only one of btruncate, dtruncate, "		///
			"or vtruncate is allowed"
		exit 498
	}
	
	local nwords : word count `anything'
	if `nwords' > 2 { // +++++++++++++++++++++++++++++++++++ invalid syntax
		di "{err}invalid syntax"
		exit 498
	}
	
	local oldobj : word 1 of `anything'
	local newobj : word 2 of `anything'
	
	if `nwords'==1 { // +++++++++++++++++++++++++++++ objname, replace
		if "`replace'"=="" {
			capture mata : SPMAT_assert_object("`oldobj'")
			if !_rc {
				di "{err}spmat object {cmd:`oldobj'} "	///
					"already exists"
				exit 498
			}
		}
	}
	
	if `nwords'==2 {
		capture mata : SPMAT_assert_object("`oldobj'")
		if _rc {
			di "{err}spmat object {cmd:`oldobj'} not found"
			exit 498
		}
		
		if "`replace'"=="" {
			capture mata : SPMAT_assert_object("`newobj'")
			if !_rc {
				di "{err}object {cmd:`newobj'} already exists"
				exit 498
			}
		}
	}
	
	if "`btruncate'"!="" {
		local atbin : word 1 of `btruncate'
		local nbins : word 2 of `btruncate'
		
		if `nbins' < 2 {
			di "{err}number of bins must be greater than 1"
			exit 498
		}
		
		if `nbins' <= `atbin' {
			di "{err}number of bins must be greater "	///
				"than the bin you are truncating at"
			exit 498
		}
		mata : SPMAT_truncate_object("`oldobj'",	///
			"`newobj'",1,`atbin',`nbins',"`replace'",0)
	}
	else if "`dtruncate'"!="" {
		local below : word 1 of `dtruncate'
		local above : word 2 of `dtruncate'
		if "`above'"=="" local above = `below'
		mata : SPMAT_truncate_object("`oldobj'",	///
			"`newobj'",2,`below',`above',"`replace'",0)
	}
	else if "`vtruncate'"!="" {
		local nval : word count `vtruncate'
		if `nval'>1 {
			di "{err}{cmd:vtruncate()} accepts only one number"
			exit 498
		}
		capture local num1 = `vtruncate'
		if _rc {
			di "{err}{cmd:`vtruncate'} is not a valid number"
			exit 498
		}
		capture confirm number `num1'
		if _rc {
			di "{err}{cmd:`vtruncate'} did not evaluate to a number"
			exit 498
		}
		mata : SPMAT_truncate_object("`oldobj'",	///
			"`newobj'",3,`num1',0,"`replace'",0)
	}
	else {
		mata : SPMAT_truncate_object("`oldobj'",	///
			"`newobj'",4,0,0,"`replace'",0)
	}
	
end

program define SPMAT_dta
	
	syntax anything [if] [in] ,			///
		[ 					///
		id(varname numeric) 			///
		rcv					///
	      	IDISTance 				///
		NORMalize(string)			///
		replace 				///
		force					///
	    	]
	
	if "`rcv'" != "" & "`id'" != "" {
		opts_exclusive "id() rcv"
	}
	
	gettoken objname vars : anything
	unab varlist : `vars'
	
	confirm variable `varlist'
	
	SPMAT_normparse,  `normalize'
	local normalize `r(normalize)'
	
	marksample touse
		
	if ("`replace'" != "") capture spmat drop `objname'
	
	if "`rcv'" != "" {
		mata: SPMAT_putdta_rcv("`objname'","`varlist'","`touse'", ///
			"`idistance'","`normalize'")
		exit
	}
	
	tempname mat vec
	c_local matanames `mat' `vec'
	mata : `mat' = st_data(.,"`varlist'","`touse'")
	if ("`id'" != "") {
		mata: `vec' = st_data(.,"`id'","`touse'")
	}
	else mata: `vec' = J(0,1,0)
	
	mata : SPMAT_putdta("`objname'",`mat',"`idistance'","`normalize'",`vec')
	
end

program define SPMAT_permute
	
	syntax anything
	
	local nwords : word count `anything'
	if `nwords' != 2 {
		di "{err}spmat permute requires two arguments"
		exit 498
	}
	
	gettoken objname sortvar : anything
	local sortvar = trim("`sortvar'")
	
	// check objname
	
	capture mata : SPMAT_assert_object("`objname'")
	if _rc SPMAT_error `objname'
	
	// check sortvar
	
	capture confirm numeric variable `sortvar'
	if _rc {
		di "{err}`sortvar' must be a numeric variable"
		exit 498
	}
	capture assert `sortvar' != .
	if _rc {
		di "{err}`sortvar' contains missing values"
		exit 498
	}
	
	capture mata : SPMAT_permute("`objname'", "`sortvar'")
	if _rc SPMAT_error `objname'
	
end

program define SPMAT_read

	syntax name using/ [, replace ]
	
	if "`replace'" != "" {
		capture spmat drop `namelist'
	}
	
	mata : SPMAT_read_u("`namelist'",`"`using'"') 

end

program define SPMAT_import

	syntax name using/, [ 			///
		noID 				///
		nlist 				///
		geoda 				///
		NORMalize(string)		///
		IDISTance 			///
		replace 			///
		force				///
		]
	
	if ("`nlist'"=="nlist" & "`idistance'"=="idistance") {
	      di "{err}idistance not allowed with nlist"
	      exit(498)
	}
	
	if ("`id'"!="" & "`nlist'"=="nlist") {
		di "{err}option {cmd:noid} not allowed with nlist"
		exit 498
	}
	
	if ("`id'"!="" & "`geoda'"!="") {
		di "{err}option {cmd:noid} not allowed with geoda"
		exit 498
	}
	
	SPMAT_normparse,  `normalize'
	local normalize `r(normalize)'
	
	if "`replace'" != "" {
		capture spmat drop `namelist'
	}
	
	mata : SPMAT_import_u("`namelist'", `"`using'"', "`id'",	 ///
		"`nlist'", "`geoda'", "`idistance'","`normalize'")
	
end

program define SPMAT_export

	syntax name using/, [ noID nlist replace DELIMiter(string) ] 

	if "`replace'" != "" {
		local rpl 1
	}
	else local rpl 0
	
	if ("`id'"!="" & "`nlist'"!="") {
		di "{err}noid not allowed with nlist"
		exit 498
	}
	
	capture mata : SPMAT_assert_object("`namelist'")
	if _rc SPMAT_error `namelist'
	
	mata : SPMAT_export_u("`namelist'",`"`using'"',"`id'",`rpl', "`nlist'")
	
end

program define SPMAT_save

	syntax name using/ [, replace ]

	if "`replace'" == "" {
		local replace 0
	}
	else local replace 1
	
	capture mata : SPMAT_assert_object("`namelist'")
	if _rc SPMAT_error `namelist'
	
	mata : SPMAT_save_u("`namelist'",`"`using'"',`replace')
	if _rc SPMAT_error `namelist'

end

program define SPMAT_drop

	capture syntax name
	
	capture mata : SPMAT_assert_object("`namelist'")
	local rc = _rc
	
	capture mata : mata drop `namelist'
	
	if (`rc') di "{txt}(note: spmat object {cmd:`namelist'} not found)"
	
end

program define SPMAT_normparse, rclass

	capture syntax , [row SPEctral MINmax]

	if _rc {
		local 0 : subinstr local 0 "," ""
		local 0  = trim("`0'")
		di as err `"invalid {cmd:normalize(`0')}"'
		exit 498
	}

	if "`row'" != "" {
		return local normalize "row"
		exit
	}	
	else if "`spectral'" != "" {
		return local normalize "spectral"
		exit
	}	
	else if "`minmax'" != "" {
		return local normalize "minmax"
		exit
	}	
	else {
		return local normalize 
	}
end

program define SPMAT_graph

	syntax name(name=spmat) 	///
		[ , 			///
		BLocks(string)		///
		XTItle(passthru)	///
		YTItle(passthru)	///
		XLABel(passthru)	///
		YLABel(passthru)	///
		XSCale(passthru)	///
		YSCale(passthru)	///
		ASPECTratio(passthru)	///
		LEGend(passthru)	///
		by(passthru)		///
		plain			///  UNDOCUMENTED
		*			///
		]
	
	if `"`by'"' != "" {
		di "{err}option by() not allowed"
		exit 191
	}
	
	capture mata : SPMAT_assert_object("`spmat'")
	if _rc SPMAT_error `spmat'
	
	preserve
	
	qui drop _all
	
	tempvar row column yy xx color id
	
	if "`blocks'" != "" {
		gettoken b1 b2 : blocks, parse("() ") match(parens)
		if "`b2'"=="" {
			local blocks `b1'
			local stat = "max"	/* default statistic */
		}
		else {
			local blocks `b2'
			local stat `b1'
		}
	
		confirm integer number `blocks'
		
		if `blocks' < 1 {
			di "{err}number of blocks must be >0"
			exit 498
		}
	}
	else {
		local blocks 1
		local stat = "max"	// default statistic
	}
	
	qui gen double `color' = .
	
	mata : SPMAT_make("`spmat'","`color'",`blocks',"`stat'")
	
	qui gen `column' = int((_n-1)/`dim') + 1  // dim created in SPMAT_make
	sort `column' 
	qui by `column': gen `row' = _n
	qui expand 5
	
	sort `row' `column'
	qui by `row' `column': gen `id' = _n
	qui gen `xx' = cond(`id'<3, `column'-1, 	///
				cond(`id'<5, `column', .))
	qui gen `yy' = cond(`id'==2 | `id'==3, -`row',	///
			cond(`id'==1 | `id'==4, -`row'+1, .))
	qui replace `yy' = abs(`yy')
	qui levels `color', local(shades)
	
	// invert shades list
	mata : SPMAT_invert_shades("`shades'")
	local shades = r(shadelist)
	
	local area = ""
	foreach i of local shades {
		local area `area' (area `yy' `xx' if `color'==`i', ///
		    cmiss(n) bfintensity(100) blw(vvvthin) bc(gs`i') nodropb)
	}
	
	if "`plain'"=="plain" {
		twoway `area' ,						///
			aspectratio(1) legend(off)			///
			xlabel(minmax,nolabels nogrid tstyle(none)) 	///
			ylabel(minmax,nolabels nogrid tstyle(none))	///
			xscale(off) yscale(off reverse) 		///
			`options'
	}
	else {
		
		if (`dim'<=12) {
			local ticks = "1(1)`dim'"
		}
		else {
			qui summ `xx'
			local max = `r(max)'
			
			_natscale 1 `max' 5
			local natmax = `r(max)'
			local delta = round(`r(delta)',1)
			if (`delta'<1) local delta = 1
			
			local ticks 1
			local curr 0
			local cond = `max'-`delta'
			
			while (`curr'<=`cond') {
				local curr = `curr' + `delta'
				if (`max'-`curr' >= .5*`delta') {
					local ticks "`ticks' `curr'"
				}
			}
			
			local ticks "`ticks' `max'"
		}
		
		qui replace `xx' = `xx' + .5
		qui replace `yy' = `yy' + .5
		
		if `"`aspectratio'"'=="" local aspectratio "aspectratio(1)"
		if `"`legend'"'=="" local legend "legend(off)"
		if `"`xtitle'"'=="" local xtitle "xtitle(Columns)"
		if `"`ytitle'"'=="" local ytitle "ytitle(Rows)"
		if `"`xlabel'"'=="" local xlabel "xlabel(`ticks', nogrid)"
		if `"`ylabel'"'=="" {
			local ylabel "ylabel(`ticks', nogrid angle(0))"
		}
		if `"`xscale'"'=="" local xscale = "xscale(alt)"
		if `"`yscale'"'=="" local yscale = "yscale(reverse)"
		
		
		twoway `area' , 					///
			`aspectratio' `legend' `xtitle' `ytitle'	///
			`xlabel' `ylabel' `xscale' `yscale' `options'
	}
	
	restore
	
end

program define SPMAT_lag, sortpreserve

	syntax anything [, id(varname numeric) ]
	
	local n : word count `anything'
		
	if (`n'<3 | `n'>4) {
		di "{err}invalid syntax"
		error 498
	}
	
	local type : word 1 of `anything'
	
	if (`n'==4 & "`type'"!="double" & "`type'"!="float") {
		di "{err}invalid type"
		exit 498
	}
	
	if `n'==4 {
		gettoken type anything : anything
	}
	else {
		local type = "float"
	}
	
	gettoken newvar anything : anything
	gettoken objname oldvar : anything
	local oldvar = trim("`oldvar'")
	confirm new variable `newvar'
	
	capture mata : SPMAT_assert_object("`objname'")
	if _rc SPMAT_error `objname'
	
	if "`id'"!="" {
		spmat idmatch `objname' , id("`id'")
	}
	
	mata : SPMAT_lag("`type'","`newvar'","`objname'","`oldvar'")
	
end

program define SPMAT_eigenvalues
	
	syntax anything [, EIGenvalues(string) replace drop]
	
	local n : word count `anything'
		
	if `n'>1 {
		di "{err}invalid syntax"
		error 498
	}
	
	local objname : word 1 of `anything'
	
	if "`drop'"=="drop" local replace = "drop"
	
	capture mata : SPMAT_assert_object("`objname'")
	if _rc SPMAT_error `objname'
	
	mata : SPMAT_eigenvalues("`objname'","`replace'",`eigenvalues')
	
end

program define SPMAT_idmatch
	
	syntax anything , id(varname numeric)
	
	/* sorts the dataset in memory on id variable such that the 
		id variable matches the ids contained in spmat object */
	
	local n : word count `anything'
		
	if `n'>1 {
		di "{err}invalid syntax"
		error 498
	}
	
	local objname : word 1 of `anything'
	
	capture mata : SPMAT_assert_object("`objname'")
	if _rc {
		di "{err}object `objname' not found"
		exit 498
	}
	
	capture assert `id' != .
	if _rc {
		di "{err}`id' contains missing values"
		exit 498
	}
	
	capture mata : SPMAT_idmatch("`objname'","`id'")
	if _rc {
		di "{err}ids in spmat object {bf:`objname'} do not match " ///
			"ids in variable {bf:`id'}"
		exit 498
	}
end

program define SPMAT_error
	args objname
	di "{err}spmat object {cmd:`objname'} not found"
	exit 498
end

exit

Version history

1.0.5

spmat dta - now supports (row, column, value) format with option -rcv-

1.0.4

spmat contiguity - option -tolerance()- is now applied and works with queen
	contiguity
	
1.0.3

spmat idistance - added check in Stata for duplicate coordinates
spmat idistance - added check in Stata for missing values in coordiate variables
spmat dta - errored out when option id() was omitted, this has been fixed

1.0.2

spmat idistance - option knn(#) is now knn(# [, ties(drop|keep)] )
spmat export - capture in front of Mata call was masking error messages
	coming from Mata code, this has been fixed
spmat getmatrix - revised syntax
spmat putmatrix - revised syntax; added error messages to make sure objname,
	matname, and vecname are different
spmat tobanded - capture in front of Mata call was masking error messages
	coming from Mata code, this has been fixed

